using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json.Linq;
using SovaDataService.SovaDataServices;
using SovaDataService.SovaModels;
using SovaWebService.ViewModels;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;


namespace SovaWebService.Controllers
{
    [Route("api/accounts")]
    public class AccountsController : Controller
    {
        private AccountDataService _dataService;
        private readonly IConfiguration _configuration;
        private string _serverSecret;
        
        /**
         * Main constructor.
         */
        public AccountsController(AccountDataService dataService, IConfiguration configuration)
        {
            _dataService = dataService;
            _configuration = configuration;
            _serverSecret = _configuration["security:key"];
        }

        /**
         * Create an account based on post body data.
         */
        [HttpPost]
        public IActionResult CreateAccount([FromBody] JObject data)
        {
            if (!data.ContainsKey("name") || !data.ContainsKey("email") || !data.ContainsKey("password"))
                return BadRequest();

            Account account = new Account()
            {
                Name = data.GetValue("name").ToString(),
                Email = data.GetValue("email").ToString(),
                Password = data.GetValue("password").ToString()
            };

            // using a Nuget package for bcrypt, see the project's website https://github.com/neoKushan/BCrypt.Net-Core
            account.Password = BCrypt.Net.BCrypt.HashPassword(account.Password);

            // here we can also implement some input validation (checking for stored XSS and such)
            if (_dataService.EmailAlreadyExists(account.Email))
                return Conflict();

            var newAccount = _dataService.Create(account);
            string link = Url.Link(nameof(GetAccount), new { id = newAccount.Id });

            var response = new
            {
                account.Id,
                account.Name,
                account.Email,
            };

            return Created(link, response);
        }

        /**
         * Retrieve the account by the given id.
         */
        [Authorize]
        [HttpGet(Name = nameof(GetAccount))]
        public IActionResult GetAccount()
        {
            int.TryParse(HttpContext.User.Identity.Name, out var userId);
            var account = _dataService.GetOne(userId);

            if (account == null)
                return NotFound();

            var response = new
            {
                account.Name,
                account.Email,
            };

            return Ok(response);
        }

        /**
         * Login based on the post data.
         */
        [HttpPost("login", Name = nameof(Login))]
        public IActionResult Login([FromBody] JObject data)
        {
            if (!data.ContainsKey("email") || !data.ContainsKey("password"))
                return BadRequest();

            string email = data.GetValue("email").ToString();
            string password = data.GetValue("password").ToString();

            // try to find the account by the email, if it can't be found, return 401 Unauthorized
            var account = _dataService.GetAccountByEmail(email);
            if (account == null)
                return Unauthorized();

            // verify the provided password
            bool passwordAccepted = BCrypt.Net.BCrypt.Verify(password, account.Password);
            if (!passwordAccepted)
                return Unauthorized();
            
            var tokenHandler = new JwtSecurityTokenHandler();
            var secret = Encoding.UTF8.GetBytes(_serverSecret);

            var tokenDescriptor = new SecurityTokenDescriptor
            {
                Subject = new ClaimsIdentity(new Claim[]
                {
                    new Claim(ClaimTypes.Name, account.Id.ToString()), 
                }),
                Expires = DateTime.Now.AddMinutes(60),
                SigningCredentials = new SigningCredentials(
                    new SymmetricSecurityKey(secret), SecurityAlgorithms.HmacSha256Signature)
            };
            var token = tokenHandler.WriteToken(tokenHandler.CreateToken(tokenDescriptor));
            
            var response = new
            {
                account.Name,
                account.Email,
                token
            };

            return Ok(response);
        }
        
        /**
         * Remove an account by id.
         */
        [Authorize]
        [HttpDelete("{id}")]
        public IActionResult DeleteAccount(int id)
        {
            bool success = _dataService.Delete(id);

            if (!success)
                return NotFound();

            return Ok();
        }
    }
}
